﻿using System;
using System.Collections.Generic;
using System.Collections;
using System.Linq.Expressions;
using System.Linq;
using System.Web;
using System.Linq.Dynamic;
using web_uchet.Models;

namespace web_uchet.Reports.OLAP
{
    /// <summary>
    /// Класс всех измерений, главное обрабатывает настройки. Вложенные или главное выполняют обработку
    /// </summary>
    public abstract class Dimension
    {
        protected const string _NewLine = "<br/>";
        /// <summary>
        /// Contructor for Dimension
        /// </summary>
        /// <param name="Set">All Settings</param>
        /// <param name="Set">Dimension Type</param>
        //protected Dimension(OLAPSettings Set, Измерения DimType)
        //{
        //    this._DimType = DimType;
        //    this.ProcessSettings(Set, DimType);
        //}
        /// <summary>
        /// Settings process for Main Dim
        /// </summary>
        /// <param name="Set"></param>
        /// 
        protected abstract void ProcessSettings(OLAPSettings Set);
        /// <summary>
        /// ключи для группировки по этому измерению по этому показателю
        /// </summary>
        protected Dictionary<Type, string> _GroupByFields = new Dictionary<Type, string>();
        /// <summary>
        ///  Выбирает конкретные данные из таблицы
        /// </summary>
        private Delegate _DataSelector;
        /// <summary>
        /// ВЫполняет предфильтрацию записи, если измерение имеет ограничения на входные данные
        /// </summary>
        protected LambdaExpression _WhereSelector;

        protected Dimension _SubDimension;
        protected Dimension _ParentDimension;
        protected Измерения _DimType;

        public Dictionary<Type, string> GroupByFields { get { return this._GroupByFields; } }
        protected virtual Delegate DataSelector { get { return this._DataSelector; } set { this._DataSelector = value; } }
        protected LambdaExpression WhereSelector { get { return this._WhereSelector; } }
        public Dimension SubDimension { get { return this._SubDimension; } }
        public Dimension ParentDimension { get { return this._ParentDimension; } }
        public void SetSubDim(Dimension Dim)
        {
            this._SubDimension = Dim;
            Dim.SetParentDim(this);
        }
        private void SetParentDim(Dimension Dim)
        {
            this._ParentDimension = Dim;
        }

        public bool HasSubDim { get { return this._SubDimension != null; ;} }
        public bool HasParentDim { get { return this._ParentDimension != null; ;} }
        public bool NeedsWhere { get { return this._WhereSelector!=null; } }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="E"></param>
        /// <returns></returns>
        /// Functions needes some refactor to be able to work with any data class
        public IQueryable/*<T>*/ Where/*<T>*/(IQueryable/*<T>*/ E)
        {
            //var Orders = E.OfType<Заказ>();
            if (E.ElementType == typeof(Заказ))
            {
                if (this._WhereSelector != null)
                    return (E as IQueryable<Заказ>).Where(this.WhereSelector as Expression<Func<Заказ, bool>>);
                else return E;
            }
            else
            {
                throw new ArgumentOutOfRangeException("Измерение не обрабатывает данные кроме заказов");
            }
        }
        
        /// <summary>
        /// Для заказов - создание списка заказов, в Менеджере каждого заказа хранится Пользователь - исполнитель или менеджер
        /// </summary>
        /// <param name="E"></param>
        /// <returns></returns>
        public Func<IQueryable, IQueryable> TransformFunction;
        public IQueryable/*<T>*/ TransForDim/*<T>*/(IQueryable/*<T>*/ E)
        {
            if (this.TransformFunction == null)
                return E;
            else return this.TransformFunction(E);
        }

        public virtual string GetDataFromGroupingKey(object Key)
        {
            return this._DataSelector.DynamicInvoke(Key
                //this.GetType().GetMethod("Cast").
                //MakeGenericMethod(this._DataSelector.Parameters[0].Type).Invoke(null, new object[] { Key })
                ) as string;
        }

        public virtual Dictionary<string, int> GetDimGraduation(IQueryable Data)
        {
            Dictionary<string, int> Result = new Dictionary<string, int>();
            
            foreach (object o in Data)
            {
                //if (o != null)
                //{
                    string s = this.GetDataFromGroupingKey(o);
                    if (!Result.ContainsKey(s))
                        Result.Add(s, 0);
                //}
            }
            int i = 0;
            Result = Result.OrderBy(e => e.Key).ToDictionary(e=>e.Key,e=>i++);
            Result.Add("Итого:", Result.Count);
            return Result;
        }
        public static T Cast<T>(object o)
        {
            return (T)o;
        }

        //protected Type _GroupingType;
        //public Type GroupingType
        //{
        //    get { return this._GroupingType; }
        //}
    }
    public class DimКлиент : Dimension
    {
        public DimКлиент(OLAPSettings Set)
        {
            this._DimType = Измерения.Клиенты;
            this.ProcessSettings(Set);          
        }
        protected override void ProcessSettings(OLAPSettings Set)
        {
            string s = string.Empty;
            if (Set.КлиентыSettings.Имя)
            {
                s += "Имя";
            }
            if (Set.КлиентыSettings.Описание)
            {
                s += (s.Length != 0 ? "+\"" + _NewLine + "\"+" : string.Empty) + "Описание_Услуги";
            }
            this.DataSelector = System.Linq.Dynamic.DynamicExpression.ParseLambda<Клиент, string>(string.Format("{0}", s)).Compile();

            string Grouping = string.Empty;
            if (Set.ПоказателиSettings.Кол_во_Заказов || Set.ПоказателиSettings.Продажи)
            {
                Grouping += "Клиент";
                this._GroupByFields.Add(typeof(Заказ), string.Format("{0}", Grouping));
            }
            else throw new ArgumentOutOfRangeException("Обработка для измерения не реализована");
        }
    }
    public class DimСотрудник : Dimension
    {
        public DimСотрудник(OLAPSettings Set)
        {
            this._DimType = Измерения.Сотрудники;
            this.ProcessSettings(Set);
        }
        protected override void ProcessSettings(OLAPSettings Set)
        {
            //SubDims and grouping
            if (Set.СотрудникиSettings.ПолномочияСотрудники)
            {
                throw new ArgumentOutOfRangeException("Группировка сотрудников по пономочиям не реализована");
            }
            else                
                {
                    string s = string.Empty;
                    if (Set.СотрудникиSettings.Имя)
                    {
                        s += "Имя_Настоящее";
                    }
                    if (Set.СотрудникиSettings.ИмяПользователя)
                    {
                        s += (s.Length != 0 ? "+\"" + _NewLine + "\"+" : string.Empty) + "ASPNetUser.UserName";
                    }
                    if (Set.СотрудникиSettings.МестоРаботы)
                    {
                        s += (s.Length != 0 ? "+\"" + _NewLine + "\"+" : string.Empty) + "Место_Работы.Название";
                    }
                    if (Set.СотрудникиSettings.Полномочия)
                    {
                        s += (s.Length != 0 ? "+\"" + _NewLine + "\"+" : string.Empty) + "Roles";
                    }
                    this.DataSelector = System.Linq.Dynamic.DynamicExpression.ParseLambda<Пользователь, string>(string.Format("{0}", s)).Compile();

                    string Grouping = string.Empty;
                    if (Set.ПоказателиSettings.Кол_во_Заказов || Set.ПоказателиSettings.Продажи)
                    {
                        Grouping += "Менеджер";
                        this._GroupByFields.Add(typeof(Заказ), string.Format("{0}", Grouping));
                    }
                    else throw new ArgumentOutOfRangeException("Обработка для измерения не реализована");
                }
        }       
    }
    public class DimВремя : Dimension
    {
        private List<DynamicProperty> _DataSelectorClassProps = new List<DynamicProperty>();
        public DimВремя(OLAPSettings Set)
        {
            this._DimType = Измерения.Время;
            this.ProcessSettings(Set);
        }
        protected override Delegate DataSelector
        {
            get
            {
                throw new NotSupportedException();
            }
            set
            {
                throw new NotSupportedException();
            }
        }
        protected override void ProcessSettings(OLAPSettings Set)
        {
            if (Set.ПоказателиSettings.Кол_во_Заказов || Set.ПоказателиSettings.Продажи)
            {
                string s = string.Empty;
                string Grouping = string.Empty;                
                if (Set.ВремяSettings.Год)
                {
                    s += "Year.ToString()+\".\"";
                    _DataSelectorClassProps.Add(new DynamicProperty("Year", typeof(int)));
                    Grouping += "Дата_Выполнения.HasValue?Дата_Выполнения.Value.Year:Дата_Поступления.Value.Year as Year";
                }
                if (Set.ВремяSettings.Месяц)
                {
                    _DataSelectorClassProps.Add(new DynamicProperty("Month", typeof(int)));
                    s += (s.Length != 0 ? ",+" : string.Empty) + "Month.ToString()+\".\"";
                    Grouping += (Grouping.Length != 0 ? "," : string.Empty) + "Дата_Выполнения.HasValue?Дата_Выполнения.Value.Month:Дата_Поступления.Value.Month as Month";
                }
                if (Set.ВремяSettings.День)
                {
                    _DataSelectorClassProps.Add(new DynamicProperty("Day", typeof(int)));
                    s += (s.Length != 0 ? ",+" : string.Empty) + "Day.ToString()+\".\"";
                    Grouping += (Grouping.Length != 0 ? "," : string.Empty) + "Дата_Выполнения.HasValue?Дата_Выполнения.Value.Day:Дата_Поступления.Value.Day as Day";
                }

                this._GroupByFields.Add(typeof(Заказ), string.Format("new({0})", Grouping));
                //this._DataSelector =
                //        DynamicExpression.ParseLambda(DynamicExpression.CreateClass(_DataSelectorClassProps), 
                //        typeof(string), string.Format("{0}", s)).Compile();

                ParameterExpression WherePar1 = Expression.Parameter(typeof(Заказ), "e");
                LambdaExpression Where = Expression.Lambda(Expression.Or(
                    Expression.Property(Expression.Property(WherePar1, "Дата_Выполнения"), "HasValue"),
                    Expression.Property(Expression.Property(WherePar1, "Дата_Поступления"), "HasValue")
                    ), WherePar1);

                this._WhereSelector = Where;
            }
            else throw new ArgumentOutOfRangeException("Время не обрабатывает данные кроме заказов");
        }
        public override string GetDataFromGroupingKey(object Key)
        {
            //var Properties=Key.GetType().GetProperties().ToDictionary(e=>e.Name,e1=>e1);
            //System.Text.StringBuilder strb = new System.Text.StringBuilder();
            //foreach (string PropName in this._DataSelectorClassProps.Select(e => e.Name))
            //{
            //    strb.AppendFormat("{0}.",Properties[PropName].GetValue(Key, null));
            //}
            System.Text.StringBuilder strb = new System.Text.StringBuilder();
            Type T = Key.GetType();
            var Properties=T.GetProperties().Select(e=>e.Name);
            if (Properties.Contains("Day"))
                strb.AppendFormat("{0}.", T.GetProperty("Day").GetValue(Key,null).ToString());
            if (Properties.Contains("Month"))
                strb.AppendFormat("{0}.", T.GetProperty("Month").GetValue(Key, null).ToString());
            if (Properties.Contains("Year"))
                strb.AppendFormat("{0}", T.GetProperty("Year").GetValue(Key, null).ToString());
            

            return strb.ToString();
        }

        public virtual IQueryable/*<T>*/ TransForDim/*<T>*/(IQueryable/*<T>*/ E)
        {
            return E;
        }
    }
    public class DimРабочиеМеста : Dimension
    {
        public DimРабочиеМеста(OLAPSettings Set)
        {
            this._DimType = Измерения.Рабочие_места;
            this.ProcessSettings(Set);
        }
        protected override void ProcessSettings(OLAPSettings Set)
        {
            string s = string.Empty;

            if (Set.РабочиеМестаSettings.Название)
            {
                s += "Название";
            }
            if (Set.РабочиеМестаSettings.ДопИнформация)
            {
                s += (s.Length != 0 ? "+\"" + _NewLine + "\"+" : string.Empty) + "Доп_Информация";
            }
            this.DataSelector = System.Linq.Dynamic.DynamicExpression.ParseLambda<Место_Работы, string>(string.Format("{0}", s)).Compile();


            if (Set.ПоказателиSettings.Кол_во_Заказов || Set.ПоказателиSettings.Продажи)
            {
                string Grouping = string.Empty;
                Grouping += "Менеджер.Место_Работы";
                this._GroupByFields.Add(typeof(Заказ), string.Format("{0}", Grouping));
            }
            else throw new ArgumentOutOfRangeException("Обработка для измерения не реализована");
        }
    }
    /// <summary>
    /// Manages hierarchy of Dims and Transformation functions of Dims
    /// </summary>
    public static class DimFactory
    {
        public static Dimension CreateDim(Измерения DimTypeToCreate, Измерения DimTypeOther, OLAPSettings Set)
        {
            Dimension Dim;
            switch (DimTypeToCreate)
            {
                case Измерения.Время:
                    return new DimВремя(Set);
                    break;
                case Измерения.Клиенты:
                    return new DimКлиент(Set);
                    break;
                case Измерения.Рабочие_места:
                    Dim = new DimРабочиеМеста(Set);
                    if (DimTypeOther!=Измерения.Сотрудники)
                    Dim.TransformFunction += DimFactory.EmployeeTransForDim;
                    return Dim;
                    break;
                case Измерения.Сотрудники:
                    if (Set.СотрудникиSettings.РабочиеМестаСотрудники)
                    {
                        Dimension РабочиеМеста = new DimРабочиеМеста(Set);
                        РабочиеМеста.TransformFunction += DimFactory.EmployeeTransForDim;
                        Set.СотрудникиSettings.РабочиеМестаСотрудники = false;
                        Dimension Сотрудники = new DimСотрудник(Set);
                        Set.СотрудникиSettings.РабочиеМестаСотрудники = true;
                        РабочиеМеста.SetSubDim(Сотрудники);
                        return РабочиеМеста;
                    }
                    else // Нет группировки и делаем обычное измерение сотрудников
                    {
                        Dim=new DimСотрудник(Set);
                        Dim.TransformFunction += DimFactory.EmployeeTransForDim;
                        return Dim;
                    }
                    break;
                default:
                    return null;
                    break;
            }
        }
        //
        private static IQueryable/*<T>*/ EmployeeTransForDim/*<T>*/(IQueryable/*<T>*/ E)
        {
                if (E.ElementType == typeof(Заказ))
                {
                    var Orders = E.OfType<Заказ>().ToList();// as IQueryable<Заказ>;
                    var a = (
                    from o in Orders
                    select new
                    {
                        Order = o,
                        Empoyee = o.Менеджер
                    }
                    );

                    var b = (
                       from o in Orders
                       select new
                       {
                           Order = o,
                           Empoyee = o.Исполнитель
                       }
                       );

                    var final = (a.Union(b)).Distinct().Select(e => new Заказ()
                    {
                        Менеджер = e.Empoyee,
                        //Исполнитель = e.Empoyee,
                        Валюта = e.Order.Валюта,
                        Выполнено = e.Order.Выполнено,
                        Дата_Выполнения = e.Order.Дата_Выполнения,
                        Дата_Поступления = e.Order.Дата_Поступления,
                        Клиент = e.Order.Клиент,
                        Отменен = e.Order.Отменен,
                        Цена = e.Order.Цена                        
                    });
                    return final.AsQueryable();
                }
                else
                {
                    throw new ArgumentOutOfRangeException("Измерение сотрудников не обрабатывает данные кроме заказов");
                }
        }
    }

    public class DateGroup
    {
        public DateGroup(int Year, int Month, int Day)
        {
            this.Year = Year;
            this.Month = Month;
            this.Day = Day;
        }
        public int Year { get; set; }
        public int Month { get; set; }
        public int Day { get; set; }
    }
}
